package weather.language;

import java.text.SimpleDateFormat;
import java.util.*;

import weather.util.Date;
import weather.util.Pair;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import weather.dictionary.*;
import weather.language.exceptions.DateNotFoundException;
import weather.language.exceptions.PlaceNotFoundException;
import weather.language.exceptions.QuestionInvalidException;
import weather.language.extractor.*;
import weather.language.forecast.*;

public class ForecastEngine 
{
	private String contextDate ;
	private String contextUserDate= "today";
	private String contextSite="Warszawa";
	
	public ForecastEngine()
	{
		initializeSiteExtractor();
		initializeKeywordsExtractor();
		contextDate = getTodayDate();
		
	}
	
	public String getForecast(String question)
	{
		String answer = "";
		try
		{
			//handle with dates
			DateExtractor de = new DateExtractor();
			String taggedQuestion = de.extractAndTag(question).get(0);
			HashMap<String, String> varToDate = de.getVarToDate();
			HashMap<String, String> varToUserDate = de.getVarToUserDate();
			if(varToDate.size()==0)
			{//dodanie kontekstu
				varToDate.put("$(date_1)", contextDate);
				varToUserDate.put("$(date_1)", contextUserDate);
				taggedQuestion+=" $(date_1)";
			}
			
			//handle with sites
			se.extract(taggedQuestion);
			taggedQuestion = se.getSentenceWithVars();
			Map<String, Site> varToSites = se.getVarToSite();
			if(varToSites.size()==0)
			{
				varToSites.put("$(site_0)", new Site(contextSite));
				taggedQuestion+= " $(site_0)";
			}
			contextSite = varToSites.get("$(site_0)").getMainName();
			//Map<String, String> varToUserSites = se.getVarToSite();
			
			wke.extract(taggedQuestion);
			taggedQuestion = wke.getSentenceWithVars();
			Map<String, WeatherGroup> varToGroup = wke.getVarToWheatherGroup();
			
			/*Map<String, WeatherGroup> varToGroup = new HashMap<String, WeatherGroup>();
			varToGroup.put("$(wkey_1)", new TemperatureGroup("HIGH"));
			taggedQuestion+=" $(wkey_1)";*/
			
			TreeMap<String, TreeMap<String, ArrayList<String>>>  dateToSite = prepareDateToSiteMap(taggedQuestion);
			if(dateToSite==null||dateToSite.size()==0)
				return "Your question does not look like a question about weather. Focus and ask again";
			
			//prepare question
			WeatherQuestion weatherQ = new WeatherQuestion();
			Set<String> keys = dateToSite.keySet();
			Iterator<String> iter = keys.iterator();
			SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yy");
			HashMap<TreeSet<WeatherGroup>, WeatherQuestion> questionFinder = new HashMap<TreeSet<WeatherGroup>, WeatherQuestion>();
			while(iter.hasNext())
			{
				 String varDate = iter.next();
				 String stringDate = "";
				 stringDate = varToDate.get(varDate);
				 //aktualizacja kontekstu
				 contextDate = stringDate;
				 contextUserDate = varToUserDate.get(varDate);
				 
				 
				 Date date = new Date(formatter.parse(stringDate).getTime());
				 date.setDescription(varToUserDate.get(varDate));
				 Set<String> sites = dateToSite.get(varDate).keySet();
				 for(String siteVar : sites)
				 {
					 WhereAndWhen wAw = new WhereAndWhen(varToSites.get(siteVar), date);
					 TreeSet<WeatherGroup> groups = new TreeSet<WeatherGroup>();
					 if(dateToSite.get(varDate).get(siteVar)==null||dateToSite.get(varDate).get(siteVar).isEmpty())
						 throw new QuestionInvalidException();
					 for(String varName: dateToSite.get(varDate).get(siteVar))
					 {
						 groups.add(varToGroup.get(varName));
					 }
					 if(questionFinder.containsKey(groups))
						 weatherQ = questionFinder.get(groups);
					 else
					 {
						 weatherQ = new WeatherQuestion();
						 questionFinder.put(groups, weatherQ);
					 }
					 for(String varName: dateToSite.get(varDate).get(siteVar))
					 {
						 varToGroup.get(varName).prepareQuestion(weatherQ);
					 }
					 weatherQ.addWhereAndWhen(wAw);
				 }
				
			}
			//Prepare question details
			//weatherQ.setQuestionAboutHighTemperature(true);
			//weatherQ.setQuestionAboutLowTemperature(true);
			//weatherQ.addQuestionAboutConditions(new ConditionsGroup("OVERALL"));
			for(WeatherQuestion wq: questionFinder.values())
			{
				answer += WeatherInformator.askForWeather(wq);
			}
		}
		catch(PlaceNotFoundException e)
		{
			return "Sorry I can not answer, because you did not specified place, that is in my dictionary";
		}
		catch(DateNotFoundException e)
		{
			return "Sorry I can not answer, because you did not specified date";
		}
		catch(QuestionInvalidException e)
		{
			return "Sorry I can not answer, because your question is not  properly formed";
		}
		catch(Exception e)
		{
			return "Sorry, but there were some problems. I can not answer because of: "+e.getMessage();
		}
		return answer;
	}
	private String getTodayDate() 
	{
		Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
	    String currentDate = sdf.format(cal.getTime());
		return currentDate;
	}

	/**
	 * 
	 * @param taggedQuestion
	 * @param varToDate
	 * @return $(date_x) -> $(site_x)
	 * @throws PlaceNotFoundException 
	 * @throws DateNotFoundException 
	 */
	protected TreeMap<String, TreeMap<String, ArrayList<String>>> prepareDateToSiteMap(String taggedQuestion) throws PlaceNotFoundException, DateNotFoundException 
	{
		String regex ="(\\$\\(date_[0-9]*\\))|(\\$\\(site_[0-9]*\\))|(\\$\\(wkey_[0-9]*\\))";
		Pattern pattern = Pattern.compile(regex);
		//TreeMap<String, ArrayList<String>> ret = new TreeMap<String, ArrayList<String>>();
		TreeMap<String, TreeMap<String, ArrayList<String>>> ret = new TreeMap<String, TreeMap<String, ArrayList<String>>>();
		
		Matcher fit = pattern.matcher(taggedQuestion);
		boolean isDateSet = false;
		TreeMap<String, ArrayList<String>> placesForDate = new TreeMap<String, ArrayList<String>>();
		TreeMap<String, ArrayList<String>> prevPlacesForDate = null;
		ArrayList<String> keyWordForPlace = new ArrayList<String>();
		ArrayList<Pair<String, String>> prevDateAndSite = new ArrayList<Pair<String, String>>(); 
		String currDate = "";
		boolean wordAdded = true;
		boolean storeNewWord = true;
		boolean keyWordUsed = false;
		while(fit.find())
		{
			if(fit.group(1)!=null)
			{//Znaleziono date
				if(isDateSet)
				{//store current and find next
					
					if(placesForDate==null)
						placesForDate = new TreeMap<String, ArrayList<String>>();
					else
						ret.put(currDate, placesForDate);
					//Handle Key word
					handleKeyWord(ret, placesForDate, keyWordForPlace, prevDateAndSite, currDate, wordAdded);
					keyWordUsed = true;
					currDate = fit.group(1);
					prevPlacesForDate = placesForDate;
					if(placesForDate!=null&&placesForDate.size()!=0)
						placesForDate = new TreeMap<String, ArrayList<String>>();
					storeNewWord = true;
					
				}
				else
				{//find places - nothing to store
					if(!placesForDate.isEmpty())
					{
						ret.put(fit.group(1), placesForDate);
						//Handle key Word
						handleKeyWord(ret, placesForDate, keyWordForPlace, prevDateAndSite, fit.group(1), wordAdded);
						keyWordUsed = true;
						currDate = fit.group(1);
						prevPlacesForDate = placesForDate;
						placesForDate =  new TreeMap<String, ArrayList<String>>();
						storeNewWord = true;
					}
					else
					{
						isDateSet = true;
						currDate = fit.group(1);
					}
				}
			}
			else if(fit.group(2)!=null)
			{
				if(keyWordForPlace.isEmpty())
					placesForDate.put(fit.group(2),null);
				else
				{
					placesForDate.put(fit.group(2),keyWordForPlace);
					keyWordUsed = true;
				}
			}
			else if(fit.group(3)!=null)
			{//key word
				//keyWordForPlace.add(fit.group(3));
				if(!placesForDate.isEmpty() || storeNewWord)
				{
					for(String place: placesForDate.keySet())
					{
						placesForDate.put(place, keyWordForPlace);
					}
					keyWordForPlace = new ArrayList<String>();
					storeNewWord = false;
				}
				if(prevDateAndSite.size()>0)
				{
					for(Pair<String,String> p: prevDateAndSite)
					{
						if(ret.get(p.getLeft()).get(p.getRight()) == null)
							ret.get(p.getLeft()).put(p.getRight(),new ArrayList<String>());
						
						ret.get(p.getLeft()).get(p.getRight()).add(fit.group(3));
					}
					placesForDate.clear();
					wordAdded = true;
				}
				else
				{
					wordAdded = false;
					keyWordForPlace.add(fit.group(3));
					keyWordUsed = false;
				}
			}
		}
		
		if(!placesForDate.isEmpty())
		{
			if(!keyWordForPlace.isEmpty()&& !keyWordUsed)
			{
				if(currDate==null || currDate.equals(""))
				{
					throw new DateNotFoundException();
				}
				
				ret.put(currDate, placesForDate);
				for(String place : placesForDate.keySet() )
				{
					ret.get(currDate).put(place,keyWordForPlace);
				}
			}
			else
			{
				if(currDate==null || currDate.equals(""))
				{
					throw new DateNotFoundException();
				}
				ret.put(currDate, placesForDate);
			}
				
		}
		if(currDate!=null&&!currDate.equals("")&&!ret.containsKey(currDate))
		{
			if(prevPlacesForDate!=null&&!prevPlacesForDate.isEmpty())
				ret.put(currDate, prevPlacesForDate);
			else
			{
				throw new PlaceNotFoundException();
			}
		}
		if(ret.size()==0&&keyWordForPlace!=null&&keyWordForPlace.size()>0)
		{
			throw new DateNotFoundException();
		}
		return ret;
	}

	private void handleKeyWord(
			TreeMap<String, TreeMap<String, ArrayList<String>>> ret,
			TreeMap<String, ArrayList<String>> placesForDate,
			ArrayList<String> keyWordForPlace,
			ArrayList<Pair<String, String>> prevDateAndSite, String currDate,
			boolean wordAdded) 
	{
		if(wordAdded==true)
		{
			for(String place : placesForDate.keySet() )
			{
				prevDateAndSite.add(new Pair<String, String>(currDate, place));
			}
			
		}
		else
		{
			for(String place : placesForDate.keySet() )
			{
				if(ret.get(currDate).get(place) == null ||ret.get(currDate).get(place).isEmpty())
					ret.get(currDate).put(place,keyWordForPlace);
			}
			prevDateAndSite.clear();
		}
	}
	
	private void initializeSiteExtractor()
	{
		se = new SiteExtractor();
		se.loadKnownSitesSet(SiteToQueryMappingDictionary.instance().getKeys());
	}
	
	private void initializeKeywordsExtractor()
	{
		wke = new WeatherKeywordExtractor();
		
		Set<WeatherGroup> groups = new HashSet<WeatherGroup>();
		
		for (ConditionsGroup cg: ConditionGroupsDictionary.instance().getGroups())
			groups.add(cg);
		
		for (TemperatureGroup tg: TemperatureGroupsDictionary.instance().getEntries())
			groups.add(tg);
		
		groups.add(new CommonGroup());
		
		wke.loadWeatherGroupsSet(groups);
	}
	
	private WeatherKeywordExtractor wke;
	private SiteExtractor se;
}
